﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Basmus.General;
using Basmus.DataStructure.Patterns;
using System.Reflection;
using System.Linq.Expressions;

namespace Basmus.DataStructure.ComponentSystem
{
	public class AttributiveNamedTuples : INamedTuples
	{
		#region ICompositor Members

		public IEnumerable<string> Names { get; private set; }
		public Func<string, Type> ElementType { get; private set; }
		public Action<object, string, Tuple> SetTuple { get; private set; }
		public Func<object, string, Tuple> GetTuple { get; private set; }

		#endregion

		private LazyLoading<string, Func<object, Tuple>> getTupleCache;
		private LazyLoading<string, Action<object, Tuple>> setTupleCache;

		public AttributiveNamedTuples(Type target)
		{
			var attrs = target.PropertiesWithNonMultipleAttribute<NamedTupleAttribute>(false).ToDictionary(x => x.Second.Name);
			Names = attrs.Keys;
			ElementType = s => attrs[s].Second.ElementType;
			Func<string, Func<object, Tuple>> getTupleCacheFunc = name =>
				{
					var targetEx = Expression.Parameter(target, "target");
					var propertyEx = Expression.Property(targetEx, attrs[name].First);
					var constructorInfo = typeof(Tuple).GetConstructor(new Type[3] { typeof(Type), typeof(object), typeof(Type) });
					var entityTypeEx = Expression.Constant(attrs[name].First.PropertyType);
					var elementTypeEx = Expression.Constant(attrs[name].Second.ElementType);
					var newTupleEx = Expression.New(constructorInfo, entityTypeEx, propertyEx, elementTypeEx);
					var lambdaEx = Expression.Lambda(newTupleEx, targetEx);
					return (Func<object, Tuple>)lambdaEx.Compile();
				};
			getTupleCache = getTupleCacheFunc.ToLazyLoading();
			Func<string, Action<object, Tuple>> setTupleCacheFunc = name =>
				{
					var entityType = attrs[name].First.PropertyType;
					var elementType = attrs[name].Second.ElementType;
					Type realType;
					bool f = entityType.IEnumerableImplemented(out realType);
					var tupleEx = Expression.Parameter(typeof(Tuple), "tuple");
					var targetEx = Expression.Parameter(target, "target");
					var tuple_EntityEx = Expression.Property(tupleEx, "Entity");
					var targetPropertyEx = Expression.Property(targetEx, attrs[name].First);
					var targetPropertySetMethod = attrs[name].First.GetSetMethod();
					var tuple_IsEnumerableEx = Expression.Property(tupleEx, "IsEnumerable");
					if (f && realType == elementType)
					{
						var targetPropertySetEx = Expression.Call(targetPropertyEx, targetPropertySetMethod, tuple_EntityEx);
						var Enumerable_EmptyEx = Expression.Call(typeof(Enumerable), "Empty", new Type[] { elementType });
						var Enumerable_DefaultIfEmptyEx = Expression.Call(typeof(Enumerable), "DefaultIfEmpty", new Type[] { elementType }, Enumerable_EmptyEx, tuple_EntityEx);
						var targetPropertySetEx2 = Expression.Call(Enumerable_DefaultIfEmptyEx, targetPropertySetMethod, tuple_EntityEx);
						var condition = Expression.Condition(tuple_IsEnumerableEx, targetPropertySetEx, targetPropertySetEx2);
						var lambdaEx = Expression.Lambda(condition, targetEx, tupleEx);
						return (Action<object, Tuple>)lambdaEx.Compile();
					}
					else
					{

						var targetPropertySetEx = Expression.Call(targetPropertyEx, targetPropertySetMethod, tuple_EntityEx);
						var Enumerable_FirstEx = Expression.Call(typeof(Enumerable), "First", new Type[] { elementType }, tuple_EntityEx);
						var targetPropertySetEx2 = Expression.Call(Enumerable_FirstEx, targetPropertySetMethod, tuple_EntityEx);
						var condition = Expression.Condition(tuple_IsEnumerableEx, targetPropertySetEx, targetPropertySetEx2);
						var lambdaEx = Expression.Lambda(condition, targetEx, tupleEx);
						return (Action<object, Tuple>)lambdaEx.Compile();
					}
				};
			setTupleCache = setTupleCacheFunc.ToLazyLoading();
		}
	}
}
